﻿using Bettzueche.RLLibrary.Functions;
using Bettzueche.RLLibrary.Types;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;

namespace RLProject1.Model.AMEEWorld
{
    /// <summary>
    /// Egozentrisches, radiales Abbild der "Real-" Welt. Entspricht der Sicht des Roboters und ist somit die 'veränderliche'
    /// Welt des Lern-Agenten.<para>
    /// Aufteilung in nummerierte Ringe (Spuren) und Sektoren. Ursprung des Abbilds ist der Mittelpunkt.
    /// Ring 0 ist der innerste Ring, aufsteigend nach außen durchnummeriert. Sektor 0 liegt auf der X-Achse (Osten), im Gegenuhrzeigersinn durchnummeriert.</para>
    /// </summary>
    public sealed class WorldImage : IStateID, ICloneable, IEnumerable<double>
    {
        /// <summary>2.0 * Math.PI</summary>
        const double zwoPI = 2.0 * Math.PI;

        /// <summary>Die Realwelt ( (x,y) -> u )</summary>
        BumpinessFunction weltFkt;

        /// <summary>Messkoords innerhalb des egozenztr. Weltbilds</summary>
        Vector[] messPunkte;

        /// <summary>Konfiguration / Tiling des Agenten-Weltmodells  / -Zustands</summary>
        Tiling[] configs;

        

        #region Properties

            #region ITypeID Members

        /// <summary>
        /// Gibt (errechnet) die ID des Zustands dieses Weltabbilds.<para>
        /// (Geerbt von IStateID)</para>
        /// </summary>
        /// <value>
        /// wird ignoriert! die ID ergibt sich aus dem Zustand dieses Objekts.
        /// </value>
        public int ID {
            get {
                return this.GibZustandsID();
            }
            set {
                /*nix*/;
            }
        }

            #endregion

        /// <summary>
        /// Gibt die Sektorenanzahl (Tortenstücke).
        /// </summary>
        /// <value>
        /// The sektoren anzahl.
        /// </value>
        public int SektorenAnzahl { get; private set; }

        /// <summary>
        /// Gibt die Spurenanzahl (Ringe).
        /// </summary>
        /// <value>
        /// The spuren anzahl.
        /// </value>
        public int SpurenAnzahl { get; private set; }

        /// <summary>
        /// Gibt die Sektorbreite in Radiant. Ist Abhängig von SektorenAnzahl. Minus Unendlich, falls SektorenAnzahl negativ oder Null.
        /// </summary>
        internal double Sektorbreite {
            get {
                if (SektorenAnzahl <= 0)
                    return Double.NegativeInfinity;
                return zwoPI / SektorenAnzahl;
            }
        }

        /// <summary>
        /// Gibt die Zielrichtung (Sektornummer Richtung Ziel).
        /// </summary>
        /// <value>
        /// Die Zielrichtung (Sektornummer Richtung Ziel).
        /// </value>
        /// <exception cref="System.ArgumentException">
        /// Zielrichtung darf nicht negativ sein.
        /// Zielrichtung darf nicht größer als SektorenAnzahl sein.
        /// </exception>
        public int Zielrichtung
        { // Zielrichtung ("Richtung Sektor s") als Zustand!?!
            get
            {
                return _tDir;
            }
            internal set
            {
                if (value < 0) {
                    throw new ArgumentException("Zielrichtung darf nicht negativ sein.");
                }
                if (value > this.SektorenAnzahl) {
                    throw new ArgumentException("Zielrichtung darf nicht größer als SektorenAnzahl sein.");
                }
                _tDir = value;
            }
        }
        int _tDir;

        /// <summary>
        /// Gets the Anzahl Messwinkel je Sektor.
        /// </summary>
        /// <value>
        /// The anzahl messwinkel je sektor.
        /// </value>
        internal int AnzahlMesswinkelJeSektor { get; private set; }

        /// <summary>
        /// Gets the Anzahl Messreihen je Spur.
        /// </summary>
        /// <value>
        /// The anzahl messreihen je spur.
        /// </value>
        internal int AnzahlMessreihenJeSpur { get; private set; }

        #endregion


        #region Indexer

        /// <summary>
        /// Gets or sets das Unebenheitsmaß im spezifierten Abschnitt (Spur, Sektor). Teil des Weltzustands, neben der Zielrichtung.<para>
        /// Ring 0 ist der innerste Ring, aufsteigend nach außen durchnummeriert. Sektor 0 liegt auf der X-Achse (Osten), im Gegenuhrzeigersinn durchnummeriert.
        /// </para>
        /// </summary>
        /// <value>
        /// Der <see cref="System.Double"/>-Wert für das Unebenheitsmaß (Normalerweise ein Wert von 0 bis 1).
        /// </value>
        /// <param name="sektor">Der wievielte Sektor. 0 entspricht Ost bzw. X-Achse, gegen Uzs gezählt.</param>
        /// <param name="spur">Die wievielte Spur. 0 ist innen.</param>
        /// <returns>Durchsnittliche Unebenheit im spezifitierten Abschnitt. Wert ca. im Intervall [0..1]</returns>
        /// <exception cref="System.IndexOutOfRangeException">Sektor oder Spur negativ. Sektor &gt; SektorenAnzahl-1. Spur &gt; SpurenAnzahl-1</exception>
        public double this[int spur, int sektor]
        {
            get
            {
                // Sektor Ost, NO, N,... Spur0. Dann O, NO,... Spur 1, etc.
                return unebenheiten[ GibAbschnittsnummer(spur,sektor) ];
            }
            internal set
            { /* set the specified index to value here */
                unebenheiten[ GibAbschnittsnummer(spur, sektor) ] = value;
            }
        }
        /// <summary>von Ost/Spur0 im Uzs bis Sued/Spur0 und dann Ost/Spur1 bis Sued/Spur1</summary>
        double[] unebenheiten;

        #endregion


        #region Konstruktoren

        // <summary>
        /// Initializes a new instance of the <see cref="WorldImage"/> class.<para>
        /// Keine Initialisierung! Erst Eigeneschaften und Weltfunktion setzen, dann init() aufrufen.</para>
        /// </summary>
        private WorldImage() {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WorldImage"/> class.<para>
        /// Standardwerte: 8 Sektoren, 2 Spuren mit Radien 0.5 m und 1 m. Je Abschnitt 5 Messwinkel und 3 Messreihen = 15 Messpunkte.</para>
        /// </summary>
        /// <param name="realWelt">Die "Weltfunktion", die zu reellen x,y Werten ein reeles "Unebenheitsmaß" wiedergibt.</param>
        WorldImage(BumpinessFunction realWelt)
            : this(8,2,new float[]{0.5f, 1f},3,5,realWelt)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WorldImage"/> class.
        /// </summary>
        /// <param name="sektoren">Anzahl Sektoren (Tortenstücke) der radialen Karte</param>
        /// <param name="spuren">Anzahl Spuren (Ringe) der radialen Karte.</param>
        /// <param name="spurenRadien">Radien der Spuren (vom Mittelpunkt).</param>
        /// <param name="messReihenJeSpur">Anzahl Messreihen je Spur.</param>
        /// <param name="messWinkelJeSpur">Anzahl Messwinkel je Spur.</param>
        /// <param name="realWelt">Die "Weltfunktion", die zu reellen x,y Werten ein reeles "Unebenheitsmaß" wiedergibt.</param>
        /// <remarks>'messReihenJeSpur' und 'messWinkelJeSpur' werden zur Ermittlung der Messpunkte bei der
        /// Abbildung von der Realwelt auf diese Karte genutzt. Pro Abschnitt (Sektor,Spur) werden dann 
        /// 'messReihenJeSpur' * 'messWinkelJeSpur' Messpunkte zu einem Unebenheitswert für den Abschnitt verrechnet.</remarks>
        WorldImage(  int sektoren, int spuren, float[] spurenRadien,
                            int messReihenJeSpur, int messWinkelJeSpur,
                            BumpinessFunction realWelt) 
        {
            SektorenAnzahl = sektoren;
            SpurenAnzahl = spuren;
            AnzahlMessreihenJeSpur = messReihenJeSpur;
            AnzahlMesswinkelJeSektor = messWinkelJeSpur;

            weltFkt = realWelt;
            init(spurenRadien);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WorldImage"/> class.
        /// </summary>
        /// <param name="weltConfig">Konfigurationsobjekt der Welt</param>
        /// <param name="messReihenJeSpur">Anzahl Messreihen je Spur.</param>
        /// <param name="messWinkelJeSpur">Anzahl Messwinkel je Spur.</param>
        /// <param name="realWelt">Die "Weltfunktion", die zu reellen x,y Werten ein reeles "Unebenheitsmaß" wiedergibt.</param>
        /// <remarks>'messReihenJeSpur' und 'messWinkelJeSpur' werden zur Ermittlung der Messpunkte bei der
        /// Abbildung von der Realwelt auf diese Karte genutzt. Pro Abschnitt (Sektor,Spur) werden dann 
        /// 'messReihenJeSpur' * 'messWinkelJeSpur' Messpunkte zu einem Unebenheitswert für den Abschnitt verrechnet.</remarks>
        public WorldImage(Tiling weltConfig, int messReihenJeSpur, int messWinkelJeSpur,
                            BumpinessFunction realWelt) 
        {
            if (weltConfig == null)
                throw new ArgumentNullException("weltConfig");
            if (realWelt == null)
                throw new ArgumentNullException("realWelt");
            if (messReihenJeSpur <= 0)
                throw new ArgumentException("Wert kleiner 1", "messReihenJeSpur");
            if (messWinkelJeSpur <= 0)
                throw new ArgumentException("Wert kleiner 1", "messWinkelJeSpur");

            SektorenAnzahl = weltConfig.Sektoren;
            SpurenAnzahl = weltConfig.Spuren;
            AnzahlMessreihenJeSpur = messReihenJeSpur;
            AnzahlMesswinkelJeSektor = messWinkelJeSpur;
            configs = new Tiling[] { weltConfig };

            weltFkt = realWelt;
            init(weltConfig.SpurRadien);
        }

        #endregion


        /// <summary>
        /// Gibt eine Kopie aller Messpunkt-Koordinaten, die für das interne Abbildung der Realwelt auf diese Karte genutzt wird.
        /// </summary>
        /// <returns>Kopie aller Messpunkt-Koordinaten</returns>
        public Vector[] GetMesspunkte() {
            Vector[] copy = new Vector[this.messPunkte.Length];
            Array.Copy(this.messPunkte, copy, messPunkte.Length);
            return copy;
        }

        /// <summary>
        /// Gibt die ID zu Abschnitt: (spur,sektor).
        /// </summary>
        /// <param name="spur">Die Spurnummer</param>
        /// <param name="sektor">Sektornummer</param>
        /// <returns>ID zu Abschnitt: (spur,sektor).</returns>
        public int GibAbschnittsnummer(int spur, int sektor) {
            return SektorenAnzahl * spur + sektor;
        }

        /// <summary>
        /// Gibt Spur- und Sektornummer zu spezifizierter Abschnittsnummer
        /// </summary>
        /// <param name="abschnittsNummer">Die Abschnittsnummer</param>
        /// <param name="spur">Out-Parameter, der die Spurnummer erhält.</param>
        /// <param name="sektor">Out-Parameter, der die Sektornummer erhält.</param>
        public void GibSpurSektor(int abschnittsNummer, out int spur, out int sektor) {
            sektor = abschnittsNummer % SektorenAnzahl;
            spur = abschnittsNummer / SektorenAnzahl;
        }

        /// <summary>
        /// Gibt eine vollständig ausgefüllte Aktion "Gehe zu spur/sektor".<para>
        /// Beinhaltet Abschnittsnummer, Sektor, Spur, (x,y)-Mittelpunkt des Abschnitts relativ zum Zentrum dieser Karte.
        /// </para>
        /// </summary>
        /// <param name="spur">The spur.</param>
        /// <param name="sektor">The sektor.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentException">spur oder sektor nummer nicht vorhanden</exception>
        public AMEEAction GetAction(int spur, int sektor) {
            // Preconditions
            if (spur < 0)
                throw new ArgumentException("Wert kleiner 0!", "spur");
            if (sektor < 0)
                throw new ArgumentException("Wert kleiner 0!", "sektor");
            if (spur > this.SpurenAnzahl-1)
                throw new ArgumentException("Wert größer als SpurenAnzahl-1!", "spur");
            if (spur > this.SektorenAnzahl - 1)
                throw new ArgumentException("Wert größer als SektorenAnzahl-1!", "sektor");

            AMEEAction retVal = new AMEEAction { SpurNummer = spur, SektorNummer = sektor };
            retVal.Abschnittsnummer = this.GibAbschnittsnummer(spur, sektor);

            this.winkelWusa(ref retVal);

            return retVal;
        }

        /// <summary>
        /// Gibt eine vollständig ausgefüllte Aktion "Gehe zu spur/sektor".<para>
        /// Beinhaltet Abschnittsnummer, Sektor, Spur, (x,y)-Mittelpunkt des Abschnitts relativ zum Zentrum dieser Karte.
        /// </para>
        /// </summary>
        /// <param name="abschnittsNummer">Abschnittsnummer</param>
        /// <param name="sektor">The sektor.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentOutOfRangeException">negativ oder größer als Anzahl Abschnitte -1</exception>
        public AMEEAction GetAction(int abschnittsNummer) {
            // Preconditions
            if (abschnittsNummer < 0)
                throw new ArgumentOutOfRangeException("spur", "Wert kleiner 0!");
            if (abschnittsNummer > ( (this.SektorenAnzahl * this.SpurenAnzahl) - 1) )
                throw new ArgumentOutOfRangeException("Wert größer als Anzahl Abschnitte!", "abschnittsNummer");

            AMEEAction retVal = new AMEEAction { Abschnittsnummer = abschnittsNummer };
            this.GibSpurSektor(abschnittsNummer, out retVal.SpurNummer, out retVal.SektorNummer);

            this.winkelWusa(ref retVal);

            return retVal;
        }

        #region private Hilfsmethoden

        /// <summary>
        /// Setzt X und Y der referenzierten AMEEAction. Sektor- und Spurnummer müssen bereits gesetzt sein.
        /// </summary>
        /// <param name="action">The action reference.</param>
        private void winkelWusa(ref AMEEAction action) {
            double winkelbreite = zwoPI / SektorenAnzahl;
            double winkel = action.SektorNummer * winkelbreite;
            float length = this.configs[0].SpurRadien[action.SpurNummer];
            float breite = length - ((action.SpurNummer <= 0) ? 0 : configs[0].SpurRadien[action.SpurNummer - 1]);
            length -= breite / 2f;
            action.X = length * Math.Cos(winkel);
            action.Y = length * Math.Sin(winkel);
        }

        /// <summary>
        /// Initialisierung. Wird vom Konstruktor aufgerufen.<para>
        /// Pro Abschnitt werden 15 Messkoordinaten ermittelt.</para><para>
        /// Diese Koordinaten werden von der Messfunktion genutzt, um mittels der Weltfunktion einen
        /// durchschnittlichen Unebenheitswert für jeden Abschnitt zu berechnen.</para>
        /// </summary>
        void init(float[] radien)
        {
            checkInitPreConditions(radien);

            unebenheiten = new double[SektorenAnzahl * SpurenAnzahl];

            int abschnitteGesamt = SektorenAnzahl * SpurenAnzahl; // 8*2 = 16 Abschnitte
            int messungenJeAbschnitt = AnzahlMesswinkelJeSektor * AnzahlMessreihenJeSpur; // 5*3 = 15 Messungen je Abschnitt
            this.messPunkte = new Vector[abschnitteGesamt * messungenJeAbschnitt]; // 240 Messpunkte gesamt

            float[][] spurenMessradien = initMessRadien(radien);

            // Jeder Sektor hat 45° Winkel. 5 Messwinkel je Sektor auf 1/10, 3/10, 1/2, 7/10, 9/10
            double sektorenWinkel = 2 * Math.PI / SektorenAnzahl; // bspw. 45° bei 8 Sektoren

            int idxOffset01 = messungenJeAbschnitt * SektorenAnzahl; // 5*3*8 = 120
            int winkelGesamt = SektorenAnzahl * AnzahlMesswinkelJeSektor; // 8*5 = 40
            double winkelOffset = sektorenWinkel / AnzahlMesswinkelJeSektor; // Messwinkelabstand in rad (bspw. 1/5 von 45° = 9°)
            double winkelTmp = (-sektorenWinkel / 2) + winkelOffset / 2; // Startwinkel Richtung Ost bzw x-Achse (bspw. -18°)

            // Berechnung der Messpunkte Koordinaten. Je 3*5=15, beginnend bei Spur0/Ost, Spur0/NO,...,Spur1/O,... 
            /*  NO-0       O-0
             * ... 17 | 14 11 ... 2 |
             * ... 16 | 13 10 ... 1 |
             * ... 15 | 12  9 ... 0 |
             */
            Parallel.For(0, winkelGesamt, w =>   // w = 0..39
            {
                for (int reihe = 0; reihe < AnzahlMessreihenJeSpur; reihe++) // j = 0..2
                {
                    int index = AnzahlMessreihenJeSpur * w + reihe;

                    for (int spur = 0; spur < SpurenAnzahl; spur++)
                    {
                        double x = spurenMessradien[spur][reihe] * Math.Cos(winkelTmp);
                        double y = spurenMessradien[spur][reihe] * Math.Sin(winkelTmp);
                        Vector koord = new Vector { X = x, Y = y };
                        messPunkte[index + (spur * idxOffset01)] = koord;
                    }
                }
                // winkel verschieben:
                winkelTmp += winkelOffset;

            });
        }

        /// <summary>
        /// Checks the init pre conditions.
        /// </summary>
        /// <param name="radien">The radien.</param>
        /// <exception cref="RLProject1.RLProjectException">
        /// ArgumentNullException(radien)
        /// or
        /// Anzahl radien ist kleiner als SpurenAnzahl
        /// or
        /// AnzahlMesswinkelJeSektor ist kleiner 0.
        /// </exception>
        /// <exception cref="System.ArgumentNullException">radien</exception>
        void checkInitPreConditions(float[] radien)
        {
            //Vorbedingungen
            if (SektorenAnzahl <= 0) {
                throw new RLProjectException("Initialisierungsfehler von WorldImage. SektorenAnzahl ist nicht positiv: " + SektorenAnzahl);
            }
            if (SpurenAnzahl <= 0) {
                throw new RLProjectException("Initialisierungsfehler von WorldImage. SpurenAnzahl ist nicht positiv " + SpurenAnzahl);
            }
            if (radien == null)
            {
                throw new RLProjectException("Initialisierungsfehler von WorldImage. Siehe innere Ausnahme für mehr Details", new ArgumentNullException("radien"));
            }
            if (radien.Length < this.SpurenAnzahl)
            {
                throw new RLProjectException("Initialisierungsfehler von WorldImage (Inkonsistenz). Anzahl radien ist kleiner als SpurenAnzahl");
            }
            Debug.WriteLineIf(radien.Length > this.SpurenAnzahl, "Inkonsistenz bei Initialisierung von WorldImage redien.Length > SpurenAnzahl.", "Warnung");
            if (AnzahlMessreihenJeSpur <= 0)
            {
                Debug.WriteLine("Inkonsistenz bei Initialisierung von WorldImage. AnzahlMesrreihenJeSpur = " + AnzahlMessreihenJeSpur + ". Wert wurde auf radien.Length gesetzt!", "Warnung");
                AnzahlMessreihenJeSpur = radien.Length;
            }
            Debug.WriteIf(0 == AnzahlMessreihenJeSpur, "Inkonsistenz bei Initialisierung von WorldImage. AnzahlMesrreihenJeSpur = 0.", "Warnung");
            if (AnzahlMesswinkelJeSektor < 0)
            {
                throw new RLProjectException("Initialisierungsfehler von WorldImage (Inkonsistenz). AnzahlMesswinkelJeSektor ist kleiner 0.");
            }
            Debug.WriteLineIf(0 == AnzahlMesswinkelJeSektor, "Inkonsistenz bei Initialisierung von WorldImage. AnzahlMesswinkelJeSektor = 0.", "Warnung");
        }


        /// <summary>
        /// Initialisiert nach Spuren sortiert die jeweiligen Messradien (bezogen auf den Ursprung).<para>
        /// Je Spur werden die 'AnzahlMessreihenJeSpur' gleichmäßig mit Abstand 1/AnzahlMessreihenJeSpur der Spurenbreite in der Spur Verteilt</para>
        /// </summary>
        /// <param name="radien">Die Radien der Spuren (bezogen auf den Ursprung). Innersten Radius zuerst.</param>
        /// <returns>Nach Spuren sortierte Messradien (bezogen auf den Ursprung). Innerster Radius hat jeweils Index 0.</returns>
        float[][] initMessRadien(float[] radien)
        {
            float[][] spurenMessradien = new float[SpurenAnzahl][];

            float teiler = 1f / (2f * AnzahlMessreihenJeSpur);
            float deltaTeiler = 1f / AnzahlMessreihenJeSpur;
            float r1 = 0f;
            int spur = 0;
            do
            {
                float r2 = radien[spur];
                float laenge = r2 - r1;
                float[] spurXMessradien = new float[AnzahlMessreihenJeSpur];// { r1 + (laenge * (0.5f - 1 / 3f)), r1 + (0.5f * laenge), r1 + (laenge * (0.5f + 1 / 3f)) };
                
                for (int reihe = 0; reihe < AnzahlMessreihenJeSpur; reihe++)
                {
                    spurXMessradien[reihe] = r1 + (laenge * teiler);
                    teiler += deltaTeiler;
                }
                spurenMessradien[spur] = spurXMessradien;
                r1 = r2;
                spur++;
            } while (spur < SpurenAnzahl);

            return spurenMessradien;
        }

        ///// <summary>
        ///// Berechnet Sektorwinkel im Bogenmaß von 0 bis 2Pi.<para>
        ///// Die Winkel haben alle ein Maß von Alpha = 2Pi / Sektoren.
        ///// Der erste Sektor wird mit Ausrichtung entlang der X-Achse angelegt und hat die Winkelgrenzen
        ///// Alpha/2 und 2Pi-Alpha/2
        ///// </para><para>
        ///// Sektor S0 hat dann die Indizes #Sektoren-1 und 0,
        ///// Sektor S1 hat die Indizes 0 und 1,...
        ///// </para>
        ///// </summary>
        //void berechneSektorwinkel() {
        //    double[] winkel = new double[this.SektorenAnzahl];
        //    double winkelbreite = zwoPI / SektorenAnzahl;
        //    winkel[0] = winkelbreite / 2.0;
        //    for (int i = 1; i < winkel.Length; i++) {
        //        winkel[i] = winkel[i - 1] + winkelbreite;
        //    }
        //    //return winkel;
        //}

        #endregion


        #region internal Methods

        /// <summary>
        /// Erstellt (misst) zur gegebenen Realwelt-Koordinate die interne (egozentrische) Kartenrepräsentation.<para>
        /// Vordefinierte Messpunkte (lokales Array) werden mit der gegebenen Koordinate verrechnet und als Parameter der
        /// Weltfunktion übergeben. Je 15 Werte pro Abschnitt in der lokalen Kartenrepräsentation werden zu einem 
        /// Durchschnittswert gemittelt und eingetragen.</para>
        /// </summary>
        /// <param name="realWeltX">The real welt X.</param>
        /// <param name="realWeltY">The real welt Y.</param>
        internal void Messe(double realWeltX, double realWeltY) {
            int abschnitteGesamt = SektorenAnzahl * SpurenAnzahl; // 8*2 = 16 Abschnitte
            int messungenJeAbschnitt = AnzahlMesswinkelJeSektor * AnzahlMessreihenJeSpur; // 5*3 = 15 Messungen je Abschnitt

            // Mittelwert aus 15 Messpunkten je Abschnitt berechnen und eintragen:
            //for(int = 0; s < abschnitteGesamt; s++)
            Parallel.For(0, abschnitteGesamt, s => {
                double sum = 0;
                for (int i = 0; i < messungenJeAbschnitt; i++) {
                    Vector messPkt = messPunkte[s * messungenJeAbschnitt + i];
                    sum += weltFkt(realWeltX + messPkt.X, realWeltY + messPkt.Y);
                }
                double mittelwert = sum / messungenJeAbschnitt;
                // Eintragen:
                this[s / SektorenAnzahl, s % SektorenAnzahl] = mittelwert;
            });

        }

        /// <summary>
        /// Setzt die Eigenschaft 'Zielrichtung' (Sektornummer) anhand des gegebenen Vektors, der ausgehend
        /// vom Mittelpunkt dieser Karte (des Agenten) die Richtung vorgibt.<para>
        /// Grenzbereiche: ...
        /// </para>
        /// </summary>
        /// <param name="agentZiel">Vektor, der ausgehend vom Mittelpunkt dieser Karte (des Agenten) die Richtung vorgibt.</param>
        internal void SetZielrichtung(Vector agentZiel) {
            double richtungsWinkel = Math.Atan2(agentZiel.Y, agentZiel.X);
            if (richtungsWinkel < 0) {
                richtungsWinkel += zwoPI;
            }
            double faktor = richtungsWinkel / this.Sektorbreite;
            int sektorNr = (int)(faktor + 0.5);
            try {
                this.Zielrichtung = sektorNr;
            }
            catch (ArgumentException ae) {
                throw new RLProjectException("Fehler bei der Berechnung der Zielrichtungssektornummer!?", ae);
            }
        }

        /*
         * je Aktion: |u(Ab0) | u(Ab1) | ... | u(AbN) | Zielrichtung |
         * 
         * Insgesamt: ((#Ziel-Sektoren * #U-Partitionen^(#Abschnitte)) = weltconfig.Dimension) * #Aktionen
         *  
         * Aktion 'Abschn. 0': | --- Ab0=u0 Werte --- | -- Ab0=u1 & Ab1=u0 Werte -- | ... | Ab2=u0 & Ab0=u0 & Z=Ab0 | Ab2=u0 & Ab0=u0 & Z=Ab1 | ...
         */

        /// <summary>
        /// Berchnet die ID dieses Weltzustands.
        /// </summary>
        /// <returns>ID des Zustands dieses Weltabbilds</returns>
        /// <remarks>
        /// Einteilung in Tabelle / Array:
        /// |u(Ab0) | u(Ab1) | ... | u(AbN) | Zielrichtung |
        /// mit u(Abn) aus [0, #U-Partitionen-1] und ZRichtung aus [0, #Sektoren-1] und
        /// N = Spuren * Sektoren
        /// </remarks>
        internal int GibZustandsID() {
            int richtung = this.Zielrichtung;
            int f_index = 0;
            int idx_offset = configs[0].Dimension;

            // je Abschnitt reduziert sich der Idx-Offset um Anz. UPartitionen
            foreach (double u in this) { // je Abschnitt der Unebenheitswert
                int uIdx = configs[0].GibUPartitionsindex(u);
                idx_offset = idx_offset / configs[0].Unebenheitspartitionen;
                f_index += uIdx * idx_offset;
            }
            f_index += richtung;

            return f_index;
        }

        #endregion


        #region IClonable Members

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// A new object that is a copy of this instance.
        /// </returns>>
        public WorldImage Clone() {
            var clone = new WorldImage();
            clone.AnzahlMessreihenJeSpur = this.AnzahlMessreihenJeSpur;
            clone.AnzahlMesswinkelJeSektor = this.AnzahlMesswinkelJeSektor;
            clone.messPunkte = new Vector[this.messPunkte.Length];
            this.messPunkte.CopyTo(clone.messPunkte, 0);
            clone.SektorenAnzahl = this.SektorenAnzahl;
            clone.SpurenAnzahl = this.SpurenAnzahl;
            clone.unebenheiten = new double[this.unebenheiten.Length];
            this.unebenheiten.CopyTo(clone.unebenheiten, 0);
            clone.weltFkt = this.weltFkt;
            clone.Zielrichtung = this.Zielrichtung;
            clone.configs = new Tiling[this.configs.Length];
            this.configs.CopyTo(clone.configs, 0); // lieber Tiling.Clone()!?
            return clone;
        }
        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// A new object that is a copy of this instance.
        /// </returns>
        object ICloneable.Clone() {
            return this.Clone();
        }
        #endregion



        #region IEnumerable
        /// <summary>
        /// Enumerator, um über die Unebenheitswerte zu iterieren, über alle Spuren, über alle Sektoren.<para>
        /// Äqivalent zu folgender Iteration:<para>
        /// <code>
        /// for (int spur = 0; spur &lt; worldImage.SpurenAnzahl; spur++) {
        ///     for (int sektor = 0; sektor &lt; worldImage.SektorenAnzahl; spur++) {
        ///         return worldImage[spur, sektor];
        ///     }
        /// }
        /// </code></para></para>
        /// </summary>
        /// <returns></returns>
        public IEnumerator<double> GetEnumerator() {
            foreach (double item in unebenheiten) {
                yield return item;
            }

            //return this.unebenheiten.GetEnumerator();
        }

        /// <summary>
        /// Returns an enumerator that iterates through the Unebenheitswerte.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
        /// </returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            return this.unebenheiten.GetEnumerator();
        } 
        #endregion

        
    }

}
